/* 
 *  splender.js
 *
 *  Copyright (C) 2007 Aaron Bockover <aaron@abock.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 *
 *  Latest updates and more information on Splender:
 *
 *     http://abock.org/project-data/splender
 *
 */

/* Default theme, can be overridden in other files
 * It's a good idea not to modify the theme here.
 */

var default_splender_theme = {
    "use_css_classes" : false,               // set to 'true' if the follow values should 
                                             // reference CSS classes instead of hardcoded styles
    
    'k' : "color: blue; font-weight: bold",  // keywords
    'c' : "color: silver",                   // comments
    's' : "color: red",                      // strings
    'h' : "color: orange",                   // chars
    'p' : "color: green"                     // preprocessor
};

/* This variable should be set/overridden if a 
 * non-default theme is desired 
 */
 
var splender_theme = null;
var splender_base_uri = null;

/* Document Loader */

if (document.addEventListener) {
    document.addEventListener ("DOMContentLoaded", function () { 
        splender_log ("DOMContentLoaded: Starting Splender");
        new Splender (); 
    }, false);
}

function splender_timer (name, func)
{
    if (typeof (console) == "undefined") {
        func ();
        return;
    }

    console.time (name);
    func ();
    console.timeEnd (name);
}

function splender_log (str)
{
    if (typeof (console) == "undefined") {
        return;
    }
    
    console.log (str);
}

function splender_load_plugin (src, id)
{
    if (!splender_base_uri) {
        var script_elems = document.getElementsByTagName ("script");
        for (var i = 0, n = script_elems.length; i < n; i++) {
            var elem = script_elems[i];
            var ofs = elem.src.lastIndexOf ('/') + 1;
            if (elem.src.substring (ofs) == "splender.js") {
                splender_base_uri = elem.src.substring (0, ofs);
            }
        }
    }
    
    document.write ("<scr"+"ipt src=\"" + splender_base_uri + src + "\" type=\"text/javascript\"></scr"+"ipt>");
}

splender_timer ("splender load external syntaxes", function () {
    splender_load_plugin ("splender-syntaxes.js", "splender-syntaxes-js");
});

/* Splender Engine */

function Splender ()
{
    initialize ();

    function initialize ()
    {
        if (splender_theme == null) {
            splender_theme = default_splender_theme;
        }

        splender_timer ("splender_highlight_elements", function () {
            highlight_elements (document.getElementsByTagName ("pre"));
        });
    }

    function highlight_elements (elements)
    {
        for (var i = 0, n = elements.length; i < n; i++) {
            var class_names = elements[i].className.split (" ");
            var theme = splender_theme;
            
            for (var j = 0, c = class_names.length; j < c; j++) {
                if (class_names[j].match ("splender_theme")) {
                    theme = eval (class_names[j]);
                    break;
                }
            }
            
            for (var j = 0, c = class_names.length; j < c; j++) {
                var splender_methods = splender_highlight_methods[class_names[j]];
                if (splender_methods != null) {
                    splender_timer ("splender_highlight_element<" + elements[i] + ">", function () {
                        highlight_element (splender_methods, elements[i], theme);
                    });
                }
            }
        }
    }

    function highlight_element (splender_methods, element, theme)
    {
        var tokens = new splender_methods[0] (element.innerHTML).lex ();
        var render = "";
        
        while (tokens.length > 0) {
            var token = tokens.shift ();
            switch (token.type) {
                case 'k':
                    render += match_keyword (token.value, splender_methods[1]) 
                        ? highlight_token (token, theme) 
                        : token.value;                
                    break;
                case 'l':
                    render += token.value;
                    break;
                default:
                    render += highlight_token (token, theme);
            }
        }
        
        element.innerHTML = render;
    }

    function highlight_token (token, theme)
    {
        var attr = theme["use_css_classes"] ? "class" : "style";
        return "<span " + attr + "='" + theme[token.type] + "'>" + token.value + "</span>";
    }

    function match_keyword (SplenderToken, keywords_func)
    {
        if (!keywords_func) {
            return false;
        }
        
        var keywords = keywords_func ();
        
        for (var i = 0, n = keywords.length; i < n; i++) {
            if (keywords[i] == SplenderToken) {
                return true;
            }
        }
        
        return false;
    }
}

/* Helper classes for the renderer and lexer implementations */

function SplenderToken (type, value)
{
    /* Types: 
          s => string
          h => char
          k => keyword
          l => literal
          c => comment
          p => preprocesor
     */
     
    this.type = type;
    this.value = value;
}

function SplenderReader (buffer)
{
    var position = 0;
    
    SplenderReader.prototype.eof     = function () { return position >= buffer.length; }
    SplenderReader.prototype.peek    = function () { return buffer[position]; }
    SplenderReader.prototype.advance = function () { return buffer[position++]; }
    SplenderReader.prototype.regress = function () { position--; }
}

/* Default lexer implementation for C-like language splenderes */

function SplenderLexer (buffer)
{
    this.reader = new SplenderReader (buffer);
    
    SplenderLexer.prototype.lex_string = function (k, cs_quoted)
    {
        var token = cs_quoted ? "@" + k : k;
        
        while (!this.reader.eof ()) {
            var c = this.reader.advance ();
            
            if ((!cs_quoted && c == '\\' && this.reader.peek () == k) ||
                (cs_quoted && c == k && this.reader.peek () == k)) {
                token += c + this.reader.advance ();
            } else {
                token += c;
                if (c == k) {
                    break;
                }
            }
        }
        
        return token;
    }
    
    SplenderLexer.prototype.lex_comment = function ()
    {
        var token = "/";
        var multiline = this.reader.peek () == '*';
        
        while (!this.reader.eof ()) {
            var c = this.reader.advance ();
            
            if (c == '\r') {
                continue;
            } else {
                token += c;
                if (c == '\n' && !multiline) {
                    break;
                } else if (multiline && c == '*' && this.reader.peek () == '/') {
                    token += this.reader.advance ();
                    break;
                }
            }
        }
        
        return token;
    }
    
    SplenderLexer.prototype.lex_preprocessor = function ()
    {
        var token = "#";
        var multiline = false;
        
        while (!this.reader.eof ()) {
            var c = this.reader.advance ();
            
            if (c == '\\' && this.reader.peek () == '\n') {
                multiline = true;
            } else if (c == '\n' && !multiline) {
                token += c;
                break;
            } else {
                multiline = false;
            }
            
            token += c;
        }
        
        return token;
    }
}

function SplenderCLexer (buffer)
{
    var lexer = new SplenderLexer (buffer);
    
    SplenderCLexer.prototype.lex = function ()
    {
        var tokens = [];
        var ktoken = "";
        
        while (!lexer.reader.eof ()) {
            var c = lexer.reader.advance ();
            switch (c) {
                case '"':
                case '\'':
                    tokens.push (new SplenderToken ('s', lexer.lex_string (c)));
                    break;
                case '#':
                    tokens.push (new SplenderToken ('p', lexer.lex_preprocessor ()));
                    break;
                case '\r':
                    break;
                case '\n': case '{':  case '}': case '(': case ')': case ';': 
                case ' ':  case '\t': case '<': case '>': case '@': case '.': case '/':
                    if (c == '/') {
                        var p = lexer.reader.peek ();
                        if (p == '/' || p == '*') {
                            tokens.push (new SplenderToken ('c', lexer.lex_comment ()));
                            break;
                        }
                    } else if (c == '@' && lexer.reader.peek () == '"') {
                        tokens.push (new SplenderToken ('s', lexer.lex_string (lexer.reader.advance (), true)));
                        break;
                    }
                
                    if (ktoken != "") {
                        tokens.push (new SplenderToken ('k', ktoken));
                        ktoken = "";
                    }
                    
                    var t_c = c;
                    if (c == '<') {
                        t_c = "&lt;";
                    } else if (c == '>') {
                        t_c = "&gt;";
                    }
                    
                    tokens.push (new SplenderToken ('l', t_c));
                    break;
                default:
                    ktoken += c;
                    break;
            }
        }
        
        return tokens;
    }
}

